//
// Copyright (c) 2009 All Right Reserved
//
// Stephen Toub
// stoub@microsoft.com
// 2009-01-01
// Contains ...
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Xml.Serialization;
using JetBrains.Annotations;
using LargoCommon.Music;
namespace LargoCommon.Midi
{
/// A collection of MIDI events.
[Serializable]
public sealed class MidiEventCollection : ICollection { //// : ICollection, ICloneable where T : new() {
#region Fields
///
/// Event List.
///
private readonly List eventList;
#endregion
#region Constructors
/// Initializes a new instance of the MidiEventCollection class.
public MidiEventCollection() {
this.eventList = new List();
}
///
/// Initializes a new instance of the MidiEventCollection class.
///
/// Midi channel.
public MidiEventCollection(MidiChannel givenChannel) {
this.eventList = new List();
this.Channel = givenChannel;
}
#endregion
#region Public Properties
///
/// Gets or sets name of the collection.
///
/// Property description.
public string Name { [UsedImplicitly] get; set; }
///
/// Gets a value indicating whether IsReadOnly.
///
/// General musical property.
public bool IsReadOnly => false;
///
/// Gets Count of events.
///
/// General musical property.
public int Count => this.EventList.Count;
///
/// Gets or sets Midi Channel.
///
/// Property description.
public MidiChannel Channel { get; set; }
///
/// Gets the break events.
///
/// Returns value.
public MidiEventCollection GetBreakEvents {
get {
var c = new MidiEventCollection();
foreach (var ev in this.EventList) {
if (ev == null) {
continue;
}
var eventType = ev.EventType;
switch (eventType) {
case "MetaTempo": {
c.Add(ev);
break;
}
case "MetaKeySignature": {
c.Add(ev);
break;
}
case "MetaTimeSignature": {
c.Add(ev);
break;
}
}
}
return c;
}
}
///
/// Gets the tempo events.
///
/// Returns value.
public MidiEventCollection GetTempoEvents {
get {
var c = new MidiEventCollection();
foreach (var ev in this.EventList) {
if (ev == null) {
continue;
}
var eventType = ev.EventType;
switch (eventType) {
case "MetaTempo": {
c.Add(ev);
break;
}
}
}
return c;
}
}
#endregion
#region Private Properties
/// Gets properties and their values.
/// Property description.
[XmlIgnore]
private List EventList {
get {
Contract.Ensures(Contract.Result>() != null);
if (this.eventList == null) {
throw new InvalidOperationException("Null event list.");
}
return this.eventList;
}
}
#endregion
#region ICollection
///
/// Add Midi Event.
///
/// Midi Event.
public void Add(IMidiEvent item) {
this.EventList.Add(item);
}
///
/// Add Range of Midi Events.
///
/// Collection of Midi events.
public void AddRange(IEnumerable collection) {
if (collection != null) {
this.EventList.AddRange(collection);
}
}
///
/// Add Range.
///
/// Collection of Midi Events.
public void AddRange(MidiEventCollection collection) {
if (collection != null) {
this.EventList.AddRange(collection);
}
}
///
/// Clear collection.
///
public void Clear() {
if (this.Count == 0 || this.EventList.Count == 0) {
return;
}
this.EventList.Clear();
}
///
/// Contains Midi Event.
///
/// Midi Event.
/// Returns value.
public bool Contains(IMidiEvent item) {
return this.EventList.Contains(item);
}
///
/// Copy To array.
///
/// Array of Midi events.
/// Array Index.
public void CopyTo(IMidiEvent[] array, int arrayIndex) {
if (arrayIndex >= array.Length || arrayIndex + this.Count > array.Length) {
return;
}
this.EventList.CopyTo(array, arrayIndex);
}
///
/// Copy To array.
///
/// Array of Midi events.
/// Array Index.
[UsedImplicitly]
public void CopyTo(Array array, int arrayIndex) {
if (array == null) {
return;
}
if (arrayIndex < 0) {
return;
}
for (var i = arrayIndex; i < this.EventList.Count; i++) {
if (i <= array.GetUpperBound(0)) {
array.SetValue(this.EventList[i], i);
}
}
//// this.eventList.CopyTo(array, arrayIndex);
}
///
/// Get Enumerator.
///
/// Returns value.
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
///
/// Get Enumerator.
///
/// Returns value.
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
///
/// Remove item.
///
/// Midi Event.
/// Returns value.
public bool Remove(IMidiEvent item) {
return this.EventList.Remove(item);
}
///
/// Event At index.
///
/// Index of event.
/// Returns value.
public IMidiEvent ElementAt(int index) {
Contract.Requires(index >= 0);
return this.EventList.ElementAt(index);
}
#endregion
///
/// Get Note On.
///
/// MIDI instrument.
/// The start time.
/// Note loudness.
///
/// Returns value.
///
[UsedImplicitly]
public VoiceNoteOn GetNoteOn(byte note, int startTime, byte loudness) {
VoiceNoteOn eventOn;
if (note > 0) {
var velocity = (byte)(note > 0 ? MusicalProperties.VelocityOfLoudness(loudness) : 0);
eventOn = new VoiceNoteOn(startTime, this.Channel, note, velocity);
}
else {
const byte velocity = 0;
eventOn = new VoiceNoteOn(startTime, this.Channel, note, velocity);
}
return eventOn;
}
///
/// Get Note Off.
///
/// MIDI instrument.
/// The midi time.
///
/// Returns value.
///
[UsedImplicitly]
public VoiceNoteOff GetNoteOff(byte note, int midiTime) {
//// byte pitchBend, MIDI pitch bend.
//// ...Pitch bend value, 0..16383, 8192 is centered.
const byte velocity = 0;
var eventOff = new VoiceNoteOff(midiTime, this.Channel, note, velocity);
return eventOff;
}
#region Older Interface
/// Inserts value of metre.
/// Rhythmical beat. Numerator of the time signature.
/// Rhythmical base. Negative power of two, denominator of time signature.
public void PutMetre(byte rhythmicBeat, byte rhythmicBase) { //// byte order
//// number of MIDI clock ticks per metronome click (0x18 = 24 ticks/click)
const byte ticks = 48;
//// number of metronome clicks per 1/4 note (The number of notated 32nd notes per MIDI quarter note?)
const byte clicks = 1;
var ts = new MetaTimeSignature(0, rhythmicBeat, rhythmicBase, ticks, clicks);
this.Add(ts);
}
/// Inserts tempo.
/// MIDI tempo = metronome clicks per minute.
public void PutTempo(int tempo) { //// byte order,
Contract.Requires(tempo != 0);
var tv = MetaTempo.MidiTempoBaseNumber / tempo; //// microseconds per metronome clicks 0
var evt = new MetaTempo(0, tv);
this.Add(evt);
}
/// Inserts tempo.
/// Metre numerator.
/// Metre base.
/// MIDI tempo.
[UsedImplicitly]
public void PutMetreAndTempo(byte rhythmicBeat, byte rhythmicBase, int tempo) { //// byte order,
Contract.Requires(tempo != 0);
this.PutMetre(rhythmicBeat, rhythmicBase); //// order
this.PutTempo(tempo);
}
/// Selects instrument and channel of the track.
/// Delta Time.///
/// MIDI instrument.
public void PutInstrument(long deltaTime, byte givenInstrument) {
var pch = new VoiceProgramChange(deltaTime, this.Channel, (MidiMelodicInstrument)givenInstrument);
this.Add(pch);
}
///
/// Puts the key signature.
///
/// The delta time.
/// The given key.
/// The given tonal genus.
public void PutKeySignature(long deltaTime, TonalityKey givenKey, TonalityGenus givenTonalGenus) {
var signature = new MetaKeySignature(deltaTime, givenKey, givenTonalGenus);
this.Add(signature);
}
#region Meta Messages
///
/// Write meta message.
///
/// The delta time.
/// Meta message text.
public void PutMetaText(long deltaTime, string text) {
var ev = new MetaText(deltaTime, text);
this.Add(ev);
}
/// Write meta message.
/// Meta message text.
public void PutMetaCopyright(string text) {
var ev = new MetaCopyright(0, text);
this.Add(ev);
}
#endregion
///
/// Inserts one note into data.
///
/// The start delta time.
/// MIDI note.
/// The stop delta time.
/// Note loudness.
/// If set to true [is from previous bar].
/// If set to true [is going to next bar].
public void PutNote(
long startTime,
byte note,
long stopTime,
byte loudness,
bool isFromPreviousBar,
bool isGoingToNextBar) { //// byte pitchBend, MIDI pitch bend.
byte velocity = 0;
if (note > 0) {
velocity = MusicalProperties.VelocityOfLoudness(loudness);
}
if (!isFromPreviousBar) {
var eventOn = new VoiceNoteOn(startTime, this.Channel, note, velocity);
this.Add(eventOn);
}
//// Staccato, Legato, ... !?!
if (isGoingToNextBar) {
return;
}
//// 2013/11 test, was stopTime only (stopTime - startTime + 1)
var eventOff = new VoiceNoteOff(stopTime, this.Channel, note, velocity); //// velocity 0?
this.Add(eventOff);
}
#endregion
#region Adding and Removing Events
/// Adds a collection of MIDI event messages to the collection.
/// The events to be added.
/// The position at which the first event was added.
[UsedImplicitly]
public int AddRange(Collection messages) { //// virtual
// Validate the input
if (messages == null) {
throw new ArgumentNullException(nameof(messages));
}
if (messages.Count == 0) {
return -1;
}
// Store the count of the list (the inserted position of the first new element).
var insertionPos = this.Count;
// Add the events
this.EventList.AddRange(messages);
// Return the position of the first
return insertionPos;
}
#endregion
#region Global operations
///
/// Increase all the delta times with given value.
///
/// Given Time.
public void AddTimeToTotals(long givenTime) {
for (var i = 0; i < this.Count; i++) {
if (i >= this.EventList.Count) {
continue;
}
if (this.EventList[i] == null) {
continue;
}
if (this.EventList[i].StartTime + givenTime >= 0) {
this.EventList[i].StartTime += givenTime;
}
}
}
/// Converts the delta times on all events to from delta times to total times.
public void RecomputeAbsoluteTimes() { //// ConvertDeltasToTotals //// [VL], was internal
Contract.Requires(this.Count > 0);
if (this.EventList == null || this.EventList.Count == 0) {
return;
}
//// Update all delta times to be total times
var ev0 = this.EventList[0];
if (this.Count <= 0 || ev0 == null) {
return;
}
var total = ev0.DeltaTime;
ev0.StartTime = total;
for (var i = 1; i < this.Count; i++) {
if (i >= this.EventList.Count) {
continue;
}
var ev = this.EventList[i];
if (ev == null) {
continue;
}
total += ev.DeltaTime;
//// ev.DeltaTime = total;
ev.StartTime = total;
}
}
/// Converts the delta times on all events from total times back to delta times.
public void RecomputeDeltaTimes() { //// ConvertTotalsToDeltas // [VL], was internal
if (this.Count == 0) {
return;
}
//// Update all total times to be deltas
long lastStartTime = 0;
for (var i = 0; i < this.Count; i++) {
if (i >= this.EventList.Count) {
continue;
}
var evi = this.EventList[i];
if (evi == null) {
continue;
}
var time = evi.StartTime - lastStartTime;
evi.DeltaTime = time >= 0 ? time : 0;
lastStartTime = evi.StartTime;
}
}
#endregion
#region Sorting
/// Sorts the events based on deltaTime.
public void SortByStartTime() { // [VL], was internal
// Sort by delta time
this.EventList.Sort(new EventComparer());
}
#endregion
#region Private methods
///
/// Get Enumerator.
///
/// Returns value.
private IEnumerator GetEnumerator() {
//// return null; // the actual implementation
return this.EventList.GetEnumerator();
}
#endregion
#region Sorting (internal only)
/// Enables comparison of two events based on delta times.
private sealed class EventComparer : IComparer {
#region Implementation of IComparer
/// Compares two MidiEvents based on delta times.
/// The first MidiEvent to compare.
/// The second MidiEvent to compare.
/// Returns -1 if x.StartTime is larger, 0 if they're the same, 1 otherwise.
public int Compare(IMidiEvent x, IMidiEvent y) {
// Get the MidiEvents
var eventX = x;
var eventY = y;
// Make sure they're valid
if (eventX == null) {
throw new ArgumentNullException(nameof(x));
}
if (eventY == null) {
throw new ArgumentNullException(nameof(y));
}
// Compare the delta times
return eventX.StartTime.CompareTo(eventY.StartTime);
}
#endregion
}
#endregion
}
}